import tf from '../../tf'
import { LoadStatus } from '../../types'
import { IndexDbLoadHandler } from './IndexDbLoadHandler'
import msgpack from 'msgpack-js'
export default class SimpleLoadHandler extends IndexDbLoadHandler {
  private modelUrl: string
  private loadByteSize: number
  private _status: LoadStatus = LoadStatus.init
  private statusChangeCallback?: (status: LoadStatus) => any = null
  private progressChangeCallback?: (progress: number) => any = null
  private _progress: number = 0
  private onLoadEnd: (data: any) => any
  public lastError: any

  set progress(value: number) {
    this._progress = value
    if (this.progressChangeCallback) {
      this.progressChangeCallback(this._progress)
    }
  }
  get progress(): number {
    return this._progress
  }
  constructor(
    modelName: string,
    modelUrl: string,
    loadByteSize: number,
    onLoadEnd: (data: any) => any
  ) {
    super(modelName)
    this.modelUrl = modelUrl
    this.loadByteSize = loadByteSize
    this.onLoadEnd = onLoadEnd
  }
  getLoadProgress(): number {
    return this.progress
  }
  onProgressChange(callback: (progress: number) => void): void {
    this.progressChangeCallback = callback
  }
  async load(): Promise<void> {
    if (typeof window === 'undefined') {
      return
    }

    if (this.getStatus() === LoadStatus.loaded) {
      console.warn('model already loaded')
      return
    }

    this.setStatus(LoadStatus.loading)
    try {
      const bytes = await this.loadBytes()
      const data = msgpack.decode(bytes)
      await tf.ready()
      this.onLoadEnd(data)
    } catch (e) {
      console.log(e)
      console.error(e)
      this.lastError = e
      this.setStatus(LoadStatus.error)
      this.progress = 0
      return
    }
    this.setStatus(LoadStatus.loaded)
    this.progress = 1
  }
  async loadBytes(): Promise<Uint8Array> {
    if (await this.supportCacheLoad()) {
      try {
        const cache = await this.getCache()
        if (!cache) throw new Error('cache is empty')
        return cache
      } catch (e) {
        console.error('缓存加载失败', e)
        throw e
      }
    }
    const xhr = new XMLHttpRequest()
    let resolve, reject
    const p = new Promise<Uint8Array>((res, rej) => {
      resolve = res
      reject = rej
    })
    xhr.open('GET', this.modelUrl, true)
    xhr.responseType = 'blob'
    xhr.onreadystatechange = async () => {
      if (xhr.readyState !== 4) {
        return
      }
      if (xhr.status !== 200) {
        if (xhr.status == 0) {
          reject(new Error('网络异常'))
          return
        }
        reject(new Error('load model error'))
        return
      }
      const blob: Blob = xhr.response
      if (!blob) {
        reject(new Error('加载的模型错误，blob为空'))
        return
      }
      const arrayBuffer = await blob.arrayBuffer()
      const uInt8Array = new Uint8Array(arrayBuffer)
      this.setCache(uInt8Array)
      resolve(uInt8Array)
    }
    xhr.onprogress = (e) => {
      if (e.lengthComputable) {
        this.progress = e.loaded / e.total
      } else {
        this.progress = Math.min(1, e.loaded / this.loadByteSize)
      }
    }
    xhr.onerror = () => {
      reject(new Error('load model error2'))
    }
    xhr.send()
    return p
  }
  onStatusChange(callback: (status: LoadStatus) => void): void {
    this.statusChangeCallback = callback
  }
  getLoadBytes(): number {
    return this.loadByteSize
  }

  private setStatus(status: LoadStatus): void {
    this._status = status
    if (this.statusChangeCallback) {
      this.statusChangeCallback(status)
    }
  }
  getStatus(): LoadStatus {
    return this._status
  }

  getModelName(): string {
    throw new Error('Method not implemented.')
  }
}
